package edu.whut.framework.util;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

/**
 * @program: MySpring
 * @description: 类工具
 * @author: Wayne
 * @create: 2020-05-24 22:28
 **/
@Slf4j
public class ClassUtil {

    public static final String FILE_Protocol = "file";

    /**
     * 根据包名来获取Class对象
     * 例如 edu.whut.entity包下有两个类Student.class University.Class
     * 那么会将这两个类的Class对象提取出来进行使用
     * @param packageName
     * @return
     */
    public static Set<Class<?>> extractPackageClass(String packageName){
        ClassLoader classLoader = getClassLoader();
        //获取包下的类编译成.class文件后的文件夹,但是需要将edu.whut.entity转换成edu/whut/entity
        URL url = classLoader.getResource(packageName.replace(".", "/"));
        if (url == null){
            throw new RuntimeException("没有找到包下的类");
        }
        Set<Class<?>> result = null;
        if (url.getProtocol().equals(FILE_Protocol)) {
            result = new HashSet<Class<?>>();
            File packageFile = new File(url.getPath());
            extractClassFile(result, packageFile, packageName);
        }
        return result;


    }

    /**
     * 返回当前线程的类加载器
     * 一般除了Bootstrap类加载器就是这个加载器了
     * @return
     */
    public static ClassLoader getClassLoader(){
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * 生成的.class文件,加载进jvm虚拟机,来实现IOC
     * @param set 存放Class对象的容器
     * @param classFile Class文件所在的目录
     * @param packageName 需要加载Class文件对象的包名
     * @return
     */
    public static void extractClassFile(final Set<Class<?>> set, File classFile, final String packageName){
        if (!classFile.isDirectory()) {
            return ;
        }
        //如果是一个文件夹，则调用其listFiles方法获取文件夹下的文件或文件夹
        File[] files = classFile.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                if(file.isDirectory()){
                    return true;
                } else{
                    //获取文件的绝对值路径
                    String absoluteFilePath = file.getAbsolutePath();
                    if(absoluteFilePath.endsWith(".class")){
                        //若是class文件，则直接加载
                        addToClassSet(absoluteFilePath);
                    }
                }
                return false;
            }
            //根据class文件的绝对值路径，获取并生成class对象，并放入classSet中
            private void addToClassSet(String absoluteFilePath) {
                //1.从class文件的绝对值路径里提取出包含了package的类名

                absoluteFilePath = absoluteFilePath.replace(File.separator, ".");
                String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName));
                className = className.substring(0, className.lastIndexOf("."));
                //2.通过反射机制获取对应的Class对象并加入到classSet里
                Class targetClass = loadClass(className);
                set.add(targetClass);
            }
        });
        if(files != null){
            for(File f : files){
                //递归调用
                extractClassFile(set, f, packageName);
            }
        }

    }

    /**
     * 通过类名直接加载类对象
     * @param className
     * @return
     */
    public static Class<?> loadClass(String className){
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            log.warn(className + "不存在");
        }
        return null;
    }
    /**
     * 实例化class
     *
     * @param clazz Class
     * @param <T>   class的类型
     * @param accessible   是否支持创建出私有class对象的实例
     * @return 类的实例化
     */
    public static <T> T newInstance(Class<T> clazz, boolean accessible){
        try {
            Constructor constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(accessible);
            return (T)constructor.newInstance();
        } catch (Exception e) {
            log.error("newInstance error", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 依赖注入为目标类注入字段实例变量
     * @param field 所属的哪个字段
     * @param targetInstance 目标实例
     * @param value 字段实例
     */
    public static void setField(Field field, Object targetInstance, Object value){
        field.setAccessible(true);
        try {
            field.set(targetInstance, value);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            log.warn("注入失败");
            throw new RuntimeException("依赖注入失败");
        }

    }

}
